﻿// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Silk.NET.BuildTools.Common;
using Silk.NET.BuildTools.Common.Builders;
using Silk.NET.BuildTools.Common.Functions;

namespace Silk.NET.BuildTools.Overloading
{
    /// <summary>
    /// Creates simple function overloads.
    /// </summary>
    ///<remarks>
    /// It is "simple" in the sense that:
    /// <list type="bullet">
    /// <item><description>The signatures it generates are SilkTouch compatible.</description></item>
    /// <item><description>No extra code is needed in order to make the function work.</description></item>
    /// </list>
    /// While this is the simplest overloading step, it is the one that has the most impact.
    /// </remarks>
    public static class SimpleParameterOverloader
    {
        /// <summary>
        /// Returns an enumerable containing the original function signature, as well as all overload variants
        /// generated by the 
        /// </summary>
        /// <param name="original">The original function signature.</param>
        /// <param name="profile">The profile. May be used by some overloads.</param>
        /// <param name="overloaders">The overloaders to use in getting function overloads.</param>
        /// <returns>An enumerable containing the original function signature and all overloads.</returns>
        public static IEnumerable<Function> GetWithOverloads
        (
            Function original,
            Profile profile,
            IEnumerable<ISimpleParameterOverloader> overloaders
        )
        {
            if (original.Parameters.Count == 0)
            {
                yield return original;
                yield break;
            }

            var parameters = original.Parameters
                .Select(x => new List<(Parameter, IPostProcessingSimpleOverloader?)> { (x, null) })
                .ToList();
            
            foreach (var parameter in parameters)
            {
                foreach (var overloader in overloaders)
                {
                    if (overloader.TryGetParameterVariant(parameter[0].Item1, out var variant, profile))
                    {
                        parameter.Add((variant, overloader as IPostProcessingSimpleOverloader));
                    }
                }
            }

            var post = new List<IPostProcessingSimpleOverloader>();
            int numGenericTypeParams;
            Parameter ProcessParameter((Parameter, IPostProcessingSimpleOverloader) input)
            {
                var (parameter, proc) = input;
                if (proc is not null)
                {
                    post.Add(proc);
                }

                return !parameter.Type.IsGenericTypeParameterReference
                    ? parameter
                    : new ParameterSignatureBuilder(parameter).WithType
                        (
                            new TypeSignatureBuilder(parameter.Type).WithName("T" + numGenericTypeParams++)
                                .Build()
                        )
                        .Build();
            }
            
            foreach (var combination in Combinations(parameters))
            {
                post.Clear();
                numGenericTypeParams = 0;
                var ret = new FunctionSignatureBuilder(original).WithParameters
                    (
                        combination.Select(ProcessParameter).ToList()
                    )
                    .WithGenericTypeParameters
                    (
                        Enumerable.Range(0, numGenericTypeParams)
                            .Select
                            (
                                x => new GenericTypeParameter
                                    {Name = "T" + x, Constraints = new List<string> {"unmanaged"}}
                            )
                            .ToList()
                    )
                    .Build();
                ret.Kind = original.Kind == SignatureKind.Normal ? ret.Parameters.SequenceEqual
                    (original.Parameters)
                    ? SignatureKind.Normal
                    : SignatureKind.SimpleOverload : original.Kind;

                foreach (var proc in post.Distinct())
                {
                    proc.Finalize(ret, profile);
                }
                    
                yield return ret;
            }
        }

        [SuppressMessage("ReSharper", "PossibleMultipleEnumeration")]
        private static IEnumerable<IReadOnlyList<T>> Combinations<T>(IEnumerable<IReadOnlyList<T>> collections)
        {
            // Any multiple enumeration will be benign and enumerating to an array or list is a needless allocation.
            if (collections.Count() == 1)
            {
                foreach (var item in collections.Single())
                    yield return new List<T> {item};
            }
            else if (collections.Count() > 1)
            {
                foreach (var item in collections.First())
                foreach (var tail in Combinations(collections.Skip(1)))
                    yield return new[] {item}.Concat(tail).ToList();
            }
        }
    }
}
